package com.atguigu.gmall.item.service.impl;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.cache.anno.GmallCache;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.feign.product.SkuDetailFeignClient;
import com.atguigu.gmall.common.cache.service.RedisCacheService;
import com.atguigu.gmall.feign.search.GoodsFeignClient;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.gmall.product.vo.AttrValueConcatVo;
import com.atguigu.gmall.item.vo.CategoryView;
import com.atguigu.gmall.product.entity.SkuInfo;

import com.atguigu.gmall.item.service.SkuInfoDetailService;
import com.atguigu.gmall.item.vo.SkuInfoDetailVo;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

@Service
@Slf4j
public class SkuInfoDetailServiceImpl implements SkuInfoDetailService  {

    @Autowired
    private SkuDetailFeignClient skuDetailFeignClient ;

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    // 布隆过滤器
    private static final BloomFilter<Long> bloomFilter = BloomFilter.create(Funnels.longFunnel(), 1000000, 0.00001);

    @Autowired
    private RedissonClient redissonClient ;

    @Autowired
    private RedisCacheService redisCacheService ;

    @Autowired
    private GoodsFeignClient goodsFeignClient ;

//    @PostConstruct       // 自定义初始化方法
    public void init() {
        Result<List<Long>> result = skuDetailFeignClient.findAllSkuIds();
        List<Long> skuIds = result.getData();
        skuIds.forEach(id -> bloomFilter.put(id));
        log.info("布隆过滤器初始化成功了....");
    }

    @GmallCache(cacheKey = "sku:info:#{#params[0]}" ,
            bloomFilterName = "skuIds-bloom-filter" ,
            bloomFilterValue = "#{#params[0]}" ,
            enableLock = true ,
            lockName = "sku:lock:#{#params[0]}" ,
            timeout = 30 ,
            timeUnit = TimeUnit.DAYS
    )
    @Override
    public SkuInfoDetailVo findBySkuId(Long skuId) {
        Result<SkuInfoDetailVo> detailVoResult = skuDetailFeignClient.findSkuInfoDetail(skuId);
        return detailVoResult.getData() ;
    }

    @Override
    public void updateHotScore(Long skuId) {

        // 对商品的热度分进行+1
        Long increment = redisTemplate.opsForValue().increment("sku:hotscore:" + skuId);
        if(increment % 5 == 0) {
            goodsFeignClient.updateHotScore(skuId ,increment);      // 远程调用service-search微服务的接口更新ES中的热度分
        }

    }

    public SkuInfoDetailVo findBySkuIdRedissonLock(Long skuId) {

        // 使用布隆过滤器
        RBloomFilter<Object> rBloomFilter = redissonClient.getBloomFilter("skuIds-bloom-filter");
        if(!rBloomFilter.contains(skuId)) {
            log.info("布隆过滤器判断没有数据，直接返回空对象； skuId: {}" , skuId);
            return new SkuInfoDetailVo() ;
        }

        // 查询数据数据
        SkuInfoDetailVo detailVo = redisCacheService.getDataFromRedis("sku:info:" + skuId, SkuInfoDetailVo.class);
        if(detailVo != null) {
            log.info("从缓存中查询到了数据， skuId: {}" , skuId);
            return detailVo ;
        }

        // 获取锁，如果获取到了锁就查询数据库，然后把查询到的数据存储到Redis中。没有获取到锁，可以进行自旋。
        RLock rLock = redissonClient.getLock("sku:lock:" + skuId);
        boolean tryLock = rLock.tryLock();
        if(tryLock) {

            try {

                log.info("{}获取到了分布式锁，查询数据库...." , Thread.currentThread().getId());

                // 查询数据库
                SkuInfoDetailVo infoDetailVo = findDataFromRpc(skuId);
                log.info("数据库中查询到了数据， skuId: {}" , skuId);
                redisCacheService.saveDataToRedis("sku:info:" + skuId , infoDetailVo , 30 , TimeUnit.DAYS);
                log.info("把从数据库中查询到的数据存储到了Redis中....");

                // 返回
                return infoDetailVo ;

            }catch (Exception e) {
                e.printStackTrace();
                return new SkuInfoDetailVo() ;      // 查询数据库失败了，返回空的对象
            } finally {
                rLock.unlock();
                log.info("{}释放了分布式锁...." , Thread.currentThread().getId());
            }

        }else {
            return findBySkuId(skuId) ;
        }

    }

    public SkuInfoDetailVo findBySkuIdLock(Long skuId) {

        // 使用布隆过滤器
        if(!bloomFilter.mightContain(skuId)) {
            log.info("布隆过滤器判断没有数据，直接返回空对象； skuId: {}" , skuId);
            return new SkuInfoDetailVo() ;
        }

        // 查询数据数据
        String skuInfoDetailVoJSON = redisTemplate.opsForValue().get("sku:info:" + skuId);
        if(!StringUtils.isEmpty(skuInfoDetailVoJSON)) {
            log.info("从缓存中查询到了数据， skuId: {}" , skuId);
            SkuInfoDetailVo skuInfoDetailVo = JSON.parseObject(skuInfoDetailVoJSON, SkuInfoDetailVo.class);
            return skuInfoDetailVo ;
        }

        // 获取锁，如果获取到了锁就查询数据库，然后把查询到的数据存储到Redis中。没有获取到锁，可以进行自旋。
        String uuid = UUID.randomUUID().toString().replace("-", "");
        Boolean aBoolean = lock(uuid , skuId);
        if(aBoolean) {      // 加锁成功

            try {

                log.info("{}获取到了分布式锁，查询数据库...." , Thread.currentThread().getId());

                // 查询数据库
                SkuInfoDetailVo infoDetailVo = findDataFromRpc(skuId);
                log.info("数据库中查询到了数据， skuId: {}" , skuId);
                redisTemplate.opsForValue().set("sku:info:" + skuId , JSON.toJSONString(infoDetailVo) , 30 , TimeUnit.DAYS);
                log.info("把从数据库中查询到的数据存储到了Redis中....");

                // 返回
                return infoDetailVo ;

            }catch (Exception e) {
                e.printStackTrace();
                return new SkuInfoDetailVo() ;      // 查询数据库失败了，返回空的对象
            } finally {
                unlock(uuid , skuId);
                log.info("{}释放了分布式锁...." , Thread.currentThread().getId());
            }

        }else {            // 加锁失败
            return findBySkuId(skuId);          // 进行自旋
        }

    }

    public Boolean lock(String uuid , Long skuId) {

        // 返回的如果是true说明加锁成功，返回的是false就说明加锁失败
        Boolean absent = redisTemplate.opsForValue().setIfAbsent("sku:lock:" + skuId, uuid, 30, TimeUnit.SECONDS);
        if(absent) {

            // 开启一个周期性执行任务，对所进行续期
            ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
            scheduledThreadPool.scheduleAtFixedRate(() -> {
                redisTemplate.expire("sku:lock:" + skuId ,30 , TimeUnit.SECONDS) ;
            } , 5 , 10 , TimeUnit.SECONDS) ;        // 5表示第一次执行任务是的延迟时间、10表示的后续执行任务时所对应的时间间隔

        }

        // 返回
        return absent ;

    }

    public void unlock(String uuid , Long skuId) {

        /**
         * lua：就是一门脚本语言(不需要进行编译，在执行的时候是通过解释器进行解释执行)
         * 官网：http://www.lua.org/manual/5.4/
         * 在Redis中如何去使用lua脚本：
         */
        String script = "if redis.call(\"get\" , KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end" ;

        // 执行lua脚本
        // RedisScript: 封装script数据
        // List<K> keys: key的list集合
        // Object... args: 参数数组
        Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("sku:lock:" + skuId), uuid);
        if(result == 1) {       // 释放锁成功了
            System.out.println("释放锁成功了...");
        }else {
            System.out.println("锁是别人的...");
        }

    }

    private SkuInfoDetailVo findDataFromRpc(Long skuId) {

        // 创建SkuInfoDetailVo对象，封装响应结果数据
        SkuInfoDetailVo skuInfoDetailVo = new SkuInfoDetailVo() ;

        // 远程调用service-product微服务中的接口根据skuId查询sku的基本数据以及图片数据
        Result<SkuInfo> skuInfoResult = skuDetailFeignClient.findSkuInfoAndImageBySkuId(skuId);
        SkuInfo skuInfo = skuInfoResult.getData();
        skuInfoDetailVo.setSkuInfo(skuInfo);
        if(skuInfo == null) {
            return null ;
        }

        // 远程调用service-product微服务中的接口根据skuId查询三级分类数据
        Result<CategoryView> categoryViewResult = skuDetailFeignClient.findCategoryViewBySkuId(skuId);
        CategoryView categoryView = categoryViewResult.getData();
        skuInfoDetailVo.setCategoryView(categoryView);

        // 设置sku的价格
        skuInfoDetailVo.setPrice(skuInfo.getPrice());

        // 远程调用service-product微服务中的接口根据skuId查询spu的销售属性和销售属性值
        Result<List<SpuSaleAttr>> saleAttrValueResult = skuDetailFeignClient.findSpuSaleAttrAndSaleAttrValue(skuId);
        List<SpuSaleAttr> saleAttrs = saleAttrValueResult.getData();
        skuInfoDetailVo.setSpuSaleAttrList(saleAttrs);

        // 远程调用service-product微服务中的接口根据skuId查询一个"字符串"
        Result<List<AttrValueConcatVo>> attrValueConcatVoResult = skuDetailFeignClient.findAttrValueConcatBySkuId(skuId);
        List<AttrValueConcatVo> concatVoList = attrValueConcatVoResult.getData();
        Map<String, Long> map = concatVoList.stream().collect(Collectors.toMap(attrValueConcatVo -> attrValueConcatVo.getAttrValueConcat(), attrValueConcatVo -> attrValueConcatVo.getSkuId()));
        skuInfoDetailVo.setValuesSkuJson(JSON.toJSONString(map));   // {"118|120":49 , "118|121":51 , "119|120":52 , "119|121":50}

        return skuInfoDetailVo ;

    }


}
